Skip to content

night-shift: gateway token auth + Origin rejection + tests - #1

Merged
syzayd merged 1 commit into
masterfrom
night-shift/2026-07-09
Jul 10, 2026
Merged

night-shift: gateway token auth + Origin rejection + tests#1
syzayd merged 1 commit into
masterfrom
night-shift/2026-07-09

Conversation

@syzayd

@syzayd syzayd commented Jul 9, 2026

Copy link
Copy Markdown
Owner

What

Adds a gateway_auth middleware to the FastAPI app (src/personal_llm/interfaces/api.py):
every request must present a shared X-DreamOS-Token header, and any request
carrying an Origin header is rejected outright (403), regardless of token
validity. The token is generated once (secrets.token_hex(32)) and persisted
to data/gateway_token (new personal_llm_gateway_token_path setting) so it
survives a restart.

Why

PROJECT-GENESIS.md section 9, Tier 1 item 6 (MASTER-FIX-PLAN.md Phase 3 item
12, finding S3): the gateway's multipart/form-encoded endpoints (voice,
vision) are CSRF-reachable from any webpage without triggering a CORS
preflight, and there was no auth at all. A real browser always sets Origin
on a cross-origin request; the CLI, tests, and any other local non-browser
caller never do - so rejecting Origin outright closes that hole without
needing a CORS allowlist.

How verified

  • tests/test_api.py (new, 8 cases, offline via the existing
    FakeRouter/store/vectors fixtures - no network, no API key): tokenless
    request rejected, wrong token rejected, correct token allowed on /stats
    and round-trips on /ask, Origin header rejected even with a valid
    token, /voice/ask requires a token, and the token file persists across
    calls.
  • Full suite: 100/100 passed (92 existing + 8 new).
  • Live end-to-end against a real uvicorn instance (per this task's "verify
    text ask, voice ask, stats end-to-end" requirement): tokenless /stats,
    /ask, /voice/ask all 401; wrong token 401; valid token + Origin
    header 403; valid token alone 200 on /stats and reaches real
    request-handling logic on /ask//voice/ask (400 on undecodable audio,
    unchanged from before this PR). /ask with a valid token hit an unrelated
    pre-existing sandbox limitation (no network access to download the
    sentence-transformers embedding model from Hugging Face Hub) - not a
    regression, and exactly why the test suite mocks embeddings via
    FakeRouter instead of relying on live network calls.

Heads-up for the next DreamOS task

DreamOS's gateway fetch calls don't send X-DreamOS-Token yet, so they will
get 401 from this gateway until DreamOS is updated to read
data/gateway_token and send it. Flagged as a follow-up in this repo's
../ai-ecosystem/NIGHT_SHIFT.md entry - intentionally left out of this PR,
which only touches personal-llm.

The gateway had no auth and multipart/form-encoded endpoints (voice,
vision) are CSRF-reachable from any webpage without a CORS preflight
(S3 in MASTER-FIX-PLAN.md). Every request now must present a shared
X-DreamOS-Token header and must not carry an Origin header - real
browser requests always set Origin on a cross-origin fetch; the CLI,
tests, and other local non-browser callers never do. The token is
generated once with secrets.token_hex(32) and persisted to
data/gateway_token so it survives a gateway restart.

Verified offline (100/100 tests green: 92 existing + 8 new in
tests/test_api.py using the FakeRouter/store/vectors fixtures - no
network, no API key). Also live-verified end-to-end against a real
uvicorn instance: tokenless /stats and /ask and /voice/ask all 401;
wrong token 401; valid token + Origin header 403; valid token alone
200 on /stats and reaches real request-handling logic on /ask and
/voice/ask (400 on undecodable audio, same as before this change).

Note for whoever picks up DreamOS next: DreamOS's gateway fetch calls
do not send this token yet, so they will get 401 until DreamOS reads
data/gateway_token and sends it as X-DreamOS-Token - flagged in
NIGHT_SHIFT.md as a follow-up, intentionally out of scope for this
PR (this repo only).
@syzayd
syzayd merged commit b10a5ec into master Jul 10, 2026
1 check passed
@syzayd
syzayd deleted the night-shift/2026-07-09 branch July 14, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants